# coding=utf-8
import json
import traceback
from abc import ABC
import tornado.web
from tormysql import DictCursor
from tornado import escape
from tornado import httputil
from tornado.log import app_log
import config
from elasticsearch import Elasticsearch
from verify import base_verify, ArgumentError
from public_libs import DateEncoder


class base_handler(tornado.web.RedirectHandler, ABC):

    def __init__(self, application, request, **kwargs):
        super().__init__(application, request, **kwargs)
        self._get_json_argument = self.load_json_argument()
        app_log.info(self._get_json_argument)
        ES_URL = self.settings.get("ES_URL")
        self.es = Elasticsearch(ES_URL, timeout=30, max_retries=10, retry_on_timeout=True)

    #  查询并返回总数
    async def find_list(self, sql, args=None):
        """
        查询数据并返回总数 需要再sql语句中添加 SQL_CALC_FOUND_ROWS
        :param sql:
        :param args:
        :return:
        """
        application = getattr(self, "application", None)
        if application:
            with (await application.db.Connection()) as conn:
                try:
                    with conn.cursor(cursor_cls=DictCursor) as cursor:
                        await cursor.execute(sql, args)
                        datas = cursor.fetchall()
                        await cursor.execute("SELECT FOUND_ROWS() as count;")
                        count = cursor.fetchall()
                        return datas, count[0].get("count")
                except Exception as e:
                    app_log.error(traceback.print_exc())
                    app_log.error("Query error: %s", e.args)
                    raise
                finally:
                    await conn.commit()

    # 返回自增id
    async def execute_auto_id(self, sql, args=None):
        """
        插入数据后返回自增id
        :param sql:
        :param args:
        :return:
        """
        application = getattr(self, "application", None)
        if application:
            with (await application.db.Connection()) as conn:
                try:
                    with conn.cursor(cursor_cls=DictCursor) as cursor:
                        await cursor.execute(sql, args)
                        await cursor.execute("SELECT LAST_INSERT_ID() as auto_id;")
                        count = cursor.fetchall()
                        return count[0].get("auto_id")
                except Exception as e:
                    app_log.error(traceback.print_exc())
                    app_log.error("Query error: %s", e.args)
                    raise
                finally:
                    await conn.commit()

    async def query_one(self, sql, args=None):
        """
        执行一个异步sql查询语句 返回第一条
        :param sql:
        :param args:
        :return:
        """
        application = getattr(self, "application", None)
        if application:
            with (await application.db.Connection()) as conn:
                with conn.cursor(cursor_cls=DictCursor) as cursor:
                    try:
                        await cursor.execute(sql, args)
                        data = cursor.fetchone()
                        return data
                    except Exception as e:
                        app_log.error(traceback.print_exc())
                        app_log.error("Query error: %s", e.args)
                        await conn.rollback()
                        raise
                    finally:
                        await conn.commit()

    async def query_all(self, sql, args=None):
        """
        执行一个异步的sql查询语句返回一个list
        :param sql:
        :param args:
        :return:
        """
        application = getattr(self, "application", None)
        if application:
            with (await application.db.Connection()) as conn:
                try:
                    with conn.cursor(cursor_cls=DictCursor) as cursor:
                        await cursor.execute(sql, args)
                        datas = cursor.fetchall()
                        return datas
                except Exception as e:
                    app_log.error(traceback.print_exc())
                    app_log.error("Query error: %s", e.args)
                    await conn.rollback()
                    raise
                finally:
                    await conn.commit()

    async def execute(self, sql, args=None):
        """
        执行一条sql语句 通常用于更新或写入
        :param sql:
        :param args:
        :return:
        """
        application = getattr(self, "application", None)
        if application:
            with (await application.db.Connection()) as conn:
                try:
                    with conn.cursor(cursor_cls=DictCursor) as cursor:
                        ret = await cursor.execute(sql, args)
                        return ret
                except Exception as e:
                    app_log.error(traceback.print_exc())
                    app_log.error("Query error: %s", e.args)
                    await conn.rollback()
                    if e.args[0] == 1406:
                        t = self.finish(self.response(status_code=4029))
                    raise
                finally:
                    await conn.commit()

    async def execute_many(self, sql, args=None):
        """
        执行一条异步sql语句 批量插入
        :param sql:
        :param args:
        :return:
        """
        application = getattr(self, "application", None)
        if application:
            with (await application.db.Connection()) as conn:
                try:
                    with conn.cursor(cursor_cls=DictCursor) as cursor:
                        for arg in args:
                            await cursor.execute(sql, arg)
                except Exception as e:
                    app_log.error(traceback.print_exc())
                    app_log.warning("Query error: %s", e.args)
                    await conn.rollback()
                    if e.args[0] == 1406:
                        t = self.finish(self.response(status_code=4029))
                    raise
                finally:
                    await conn.commit()

    @staticmethod
    def where_sql(wheres: list):
        """
        :param wheres: list [{'col': col name, 'estimate': > or < or = ....., 'value': value}]
        :return:
        """
        sql = "where 1 = 1 "
        if not wheres:
            return ""
        for option in wheres:
            sql += " and %s %s %s " % (option.get("col"), option.get("estimate"), option.get("value"))
        return sql

    def load_json_argument(self):
        """
        将请求参数json化
        :return:
        """
        try:
            return json.loads(str(self.request.body, encoding="utf-8"))
        except Exception as e:
            return {}

    async def prepare(self):
        """
        请求开始中间件
        :return:
        """
        # 初始化请求参数
        app_log.info(self.current_user)
        return self.format_argument()

    def format_argument(self):
        """
        格式化参数 验证参数
        :return:
        """
        for name, value in self.__class__.__dict__.items():
            if isinstance(value, base_verify):
                argument = self.get_json_argument.get(name, None)
                try:
                    value.initialize(name=name, argument=argument)
                    value.verify(name=name, argument=argument)
                    if value.default is not None and argument is None:
                        argument = value.default
                    setattr(self, name, argument)
                except ArgumentError as e:
                    app_log.warning(e.log_message)
                    return self.finish(self.response(status_code=4004, message=e.log_message))

    def write_error(self, status_code, **kwargs):
        self.set_header('Content-Type', 'text/plain')
        for line in traceback.format_exception(*kwargs["exc_info"]):
            app_log.error(line)
        reason = kwargs.get('message', self._reason)
        self.finish(self.response(status_code=status_code, message=reason))

    def set_status(self, status_code, reason=None):
        """Sets the status code for our response.

        :arg int status_code: Response status code. If ``reason`` is ``None``,
            it must be present in `httplib.responses <http.client.responses>`.
        :arg string reason: Human-readable reason phrase describing the status
            code. If ``None``, it will be filled in from
            `httplib.responses <http.client.responses>`.
        """
        self._status_code = status_code if httputil.responses.get(status_code) else 200
        if reason is not None:
            self._reason = escape.native_str(reason)
        else:
            try:
                self._reason = httputil.responses[status_code]
            except KeyError:
                raise ValueError("unknown status code %d" % status_code)

    def response(self, data=None, status_code=0, message=None, original=False, is_json=True):
        """
        格式化返回数据
        :param data: 需要返回的数据 dict| list
        :param status_code: 返回状态
        :param message:
        :param original: 是否原文返回
        :param is_json:
        :return:
        """
        callback = self.get_argument("callback", None)
        if data is None:
            data = {}
        if not message:
            message = config.status_message.get(status_code, "未知错误")
        if original:  # 以data原文返回
            d = data
        else:
            d = {'result': data, 'code': status_code, 'msg': message}
        app_log.info(status_code)
        app_log.info(message)
        # 适用于js callback
        if self.get_argument("callback", None):
            return callback + "(" + json.dumps(d, cls=DateEncoder) + ")" if json else callback + "(" + d + ")"
        else:
            return json.dumps(d, cls=DateEncoder) if is_json else d

    # 初始化
    def initialize(self, **kwargs):
        self.set_header('Referer', "%s://%s:%s" % (self.request.protocol, self.request.host, self.request.uri))

    # get json
    @property
    def get_json_argument(self):
        return self._get_json_argument

    # 设置默认headers
    def set_default_headers(self) -> None:
        # 自定义headers
        self.clear_header(name="Server")
